package eerbaar.shared.internal.persist.save;

import java.util.ArrayList;
import java.util.List;


import eerbaar.shared.internal.persist.PersistService;
import eerbaar.shared.persist.ISaver;
import eerbaar.shared.persist.PersistConstants;
import eerbaar.shared.tree.IAttributes;
import eerbaar.shared.tree.INode;
import eerbaar.shared.tree.IRoot;
import eerbaar.shared.tree.NodeFactory;

public class NodeSaver {

    public static class Link {

        public final INode node;

        public final ISaver<?> saveable;

        public Link(INode node, ISaver<?> saveable) {
            this.node = node;
            this.saveable = saveable;
        }

        @Override
        public boolean equals(Object obj) {

            if (obj == null) {

                return false;
            }

            if (obj == this) {

                return true;
            }

            if (obj instanceof ISaver) {

                ISaver<?> other = (ISaver<?>) obj;
                return (other == this.saveable);
            }

            return false;
        }

        @Override
        public int hashCode() {

            return toString().hashCode();
        }

        @Override
        public String toString() {

            String s = "";
            s += "link(";
            s += this.saveable.getClass().getName();
            s += ", " + this.node;
            s += ")";

            return s;
        }

    }

    private final PersistService service;

    private final NodeStack stack = new NodeStack();

    public NodeSaver(PersistService service) {
        this.service = service;
    }

    private void assignAttributes(final ISaver<?> inSaveable,
            final INode inNode) {
        assert (inSaveable != null);
        assert (inNode != null);

        ISaver<?> f = this.service.getLeastDerivedClass(inSaveable);
        if (f == null) {
            f = inSaveable;
        }

        Class<?> reg = f.getRegistertedInterface();
        if (reg != INode.class) {
            String className = reg.getName();
            IAttributes attributes = inNode.getAttributes();
            attributes.set(String.class, PersistConstants.PRIVATE_CLASS,
                    className);
        }
        f.saveAttributes(inNode);
    }

    public IRoot createNodes(ISaver<?> start) {

        List<Link> links = new ArrayList<Link>();
        return createNodes(links, start);
    }

    private IRoot createNodes(List<Link> links, ISaver<?> start) {
        assert (start != null);

        IRoot root = NodeFactory.createRoot();

        SaveableIterator i = new SaveableIterator(this.service, start);
        while (i.hasNext()) {

            SaveableIterator.Result result = i.next();

            ISaver<?> saveable = result.saveable;
            ISaver<?> parent = result.parent;

            INode node = null;

            if (parent == null) {

                node = root;
                assignAttributes(saveable, node);
            } else {

                Link link = findLink(links, parent);

                INode parentNode = link.node;

                node = NodeFactory.create(saveable.getSavableName());
                parentNode.add(node);

                assignAttributes(saveable, node);
            }

            Link link = new Link(node, saveable);
            links.add(link);
        }

        return root;
    }

    private Link findLink(List<Link> links, ISaver<?> parent) {
        Link link = null;
        for (Link l : links) {
            if (l.equals(parent)) {
                link = l;
                break;
            }
        }
        assert (link != null);
        return link;
    }

    public IRoot save(ISaver<?> start) {
        assert (start != null);

        List<Link> links = new ArrayList<Link>();
        IRoot root = createNodes(links, start);

        for (Link link : links) {
            link.saveable.saveAttributes(link.node);
        }

        return root;
    }

    @Override
    public String toString() {

        String s = "";
        s += "saver(";
        int c = 0;
        for (INode n : this.stack) {
            if (c > 0) {
                s += ", ";
            }
            s += n.getClass().getName();
            c++;
        }
        s += ")";

        return s;
    }

}
